package com.feige.netty;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.feige.netty.enums.MsgActionEnum;
import com.feige.netty.pojo.GroupDataContent;
import com.feige.netty.pojo.PrivateDataContent;
import com.feige.openfeigh.ClazzService;
import com.feige.openfeigh.CourseService;
import com.feige.openfeigh.UserCourseService;
import com.feige.pojo.Clazz;
import com.feige.pojo.Course;
import com.feige.pojo.GroupChatMsg;
import com.feige.pojo.PrivateChatMsg;
import com.feige.pojo.vo.ChatMsgVo;
import com.feige.pojo.vo.MsgVo;
import com.feige.result.R;
import com.feige.service.GroupChatMsgService;
import com.feige.service.PrivateChatMsgService;
import com.feige.service.UserService;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author feige
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    public static final String PING = "PING";
    public static final String CLAZZ = "clazz:";

    @Resource
    private PrivateChatMsgService privateChatMsgService;

    @Autowired
    private GroupChatMsgService groupChatMsgService;

    @Resource
    private UserCourseService userCourseService;

    @Resource
    private CourseService courseService;

    @Resource
    private UserService userService;

    @Resource
    private ClazzService clazzService;


    /**
     * 管理用户channel
     */
    public static Map<String, ChannelId> USERS = new HashMap<>();
    public static ChannelGroup CHANNEL_GROUPS = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        CHANNEL_GROUPS.add(channel);
        log.info("======ID为{}的channel已添加进CHANNEL_GROUPS中======",ctx.channel().id().asShortText());
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {
        Channel channel = ctx.channel();
        String text = textWebSocketFrame.text();
        PrivateDataContent privateDataContent = JSONObject.parseObject(text, PrivateDataContent.class);
        if (ObjectUtil.isNotNull(privateDataContent)){
            // 根据对应类型来执行对应的业务
            if (privateDataContent.getType().equals(MsgActionEnum.CONNECT.getType())){
                // 初始化连接
                connect(privateDataContent,channel);
            }else if (privateDataContent.getType().equals(MsgActionEnum.PRIVATE_CHAT.getType())){
                // 私聊
                privateChatMsgHandler(privateDataContent);
            }else if (privateDataContent.getType().equals(MsgActionEnum.GROUP_CHAT.getType())){
                // 群聊
                groupChatMsgHandler(text);
            } else if (privateDataContent.getType().equals(MsgActionEnum.SIGNED.getType())){
                // 签收消息类型，针对具体消息进行签收，修改数据库中对应消息的签收状态【已签收】
                //扩展字段在signed类型的消息中，代表需要去签收的消息id，逗号间隔
                String msgIdsStr = privateDataContent.getExtend();
                String[] msgIds = msgIdsStr.split(",");
                List<String> msgIdList = new ArrayList<>();
                for (String msgId : msgIds) {
                    if (StrUtil.isNotBlank(msgId)){
                        msgIdList.add(msgId);
                    }
                }
                System.out.println(msgIdList.toString());
                if (!msgIdList.isEmpty()){
                    //批量签收
                    //更新数据库数据 用foreach语句
                    privateChatMsgService.batchUpdateChatMsg(msgIdList);
                }
            }else if (privateDataContent.getType().equals(MsgActionEnum.KEEP_ALIVE.getType())){
                /*
                情况：部署到服务器上的websocket程序，未加心跳，发现隔一阵子就会断开，出现1006 (abnormal closure)错误。
			 	原因：代理参数的proxy_read_timeout默认是60s，只要超过这段时间没有通信，就会自动断开tcp连接
			 	解决办法：
			 	修改参数proxy_read_timeout，比如在代理设置添加 proxy_read_timeout 300s;
			 	添加心跳，心跳的间隔要小于默认的读超时proxy_read_timeout 60s，这样程序就会可以在60s的时间窗口内读到数据，不会被nginx断开连接。
                 */
                String ping = privateDataContent.getExtend();
                if (PING.equals(ping)){
                    channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(new MsgVo<>(3,"PONG"))));
                }
            }
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        //发生异常时关闭channel，随后从channelGroup中移除
        Channel channel = ctx.channel();
        CHANNEL_GROUPS.remove(channel);
        channel.close();
        log.error("======发生异常，ID为{}的channel从CHANNEL_GROUPS移除并关闭======",ctx.channel().id().asShortText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        if (ctx != null){
            Channel channel = ctx.channel();
            CHANNEL_GROUPS.remove(channel);
            log.info("======ID为{}的channel从CHANNEL_GROUPS移除======",ctx.channel().id().asShortText());
        }
    }



    /**
     * @description: 初始化连接，把用户ID和channel绑定到一起
     * @author: feige
     * @date: 2021/3/22 8:39
     * @param	privateDataContent	私聊消息
     * @param	channel	通道
     * @return: void
     */
    public void connect(PrivateDataContent privateDataContent,Channel channel){
        if (ObjectUtil.isNotNull(privateDataContent.getChatMsg())){
            String senderId = privateDataContent.getChatMsg().getSenderId();
            log.info("====={}进入连接=====",senderId);
            ChannelId channelId = USERS.get(senderId);
            if (ObjectUtil.isNotNull(channelId )){
                Channel c = CHANNEL_GROUPS.find(channelId);
                // 如果当前channelId和Map中的channelId不同，需用当前channelId替换Map中的channelId
                if (!(ObjectUtil.isNotNull(c) && channelId.equals(channel.id()))){
                    USERS.put(senderId, channel.id());
                    log.info("=====当前channelId替换Map中的channelId成功，{}加入连接成功=====",senderId);
                }
            }else {
                USERS.put(senderId, channel.id());
                log.info("====={}加入连接成功=====",senderId);
            }
        }
    }


   /**
    * @description: 私聊
    * @author: feige
    * @date: 2021/3/22 8:39
    * @param	privateDataContent	私聊消息
    * @return: void
    */
    public void privateChatMsgHandler(PrivateDataContent privateDataContent){
        if (ObjectUtil.isNotNull(privateDataContent.getChatMsg())){
            privateChatMsgService.save(privateDataContent.getChatMsg());
            ChatMsgVo chatMsgVo = JSONObject.parseObject(privateDataContent.getExtend(), ChatMsgVo.class);
            BeanUtils.copyProperties(privateDataContent.getChatMsg(),chatMsgVo);
            String senderId = privateDataContent.getChatMsg().getSenderId();
            chatMsgVo.setUid(senderId);
            // 私聊消息
            ChannelId channelId = USERS.get(privateDataContent.getChatMsg().getReceiverId());
            if (ObjectUtil.isNotNull(channelId)){
                Channel receiverChannel = CHANNEL_GROUPS.find(channelId);
                if (ObjectUtil.isNotNull(receiverChannel) && receiverChannel.isActive() && receiverChannel.isOpen()){
                    receiverChannel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(new MsgVo<>(1, chatMsgVo))));
                    PrivateChatMsg privateChatMsg = new PrivateChatMsg();
                    privateChatMsg.setId(privateDataContent.getChatMsg().getId());
                    privateChatMsg.setSignFlag(1);
                    privateChatMsgService.updateById(privateChatMsg);
                }
            }
        }
    }


    /**
     * @description: 群聊消息处理类
     * @author: feige
     * @date: 2021/3/22 8:39
     * @param	text 消息
     * @return: void
     */
    public void groupChatMsgHandler(String text){
        List<Object> userIds = null;
        GroupDataContent groupDataContent = JSONObject.parseObject(text, GroupDataContent.class);
        if (ObjectUtil.isNotNull(groupDataContent)){
            GroupChatMsg groupChatMsg = groupDataContent.getChatMsg();
            if (ObjectUtil.isNotNull(groupChatMsg)){
                String groupId = groupChatMsg.getGroupId();
                if (StrUtil.isNotBlank(groupId)){
                    groupChatMsgService.save(groupChatMsg);
                    ChatMsgVo chatMsgVo = JSONObject.parseObject(groupDataContent.getExtend(), ChatMsgVo.class);
                    BeanUtils.copyProperties(groupChatMsg,chatMsgVo);
                    String senderId = groupChatMsg.getSenderId();
                    chatMsgVo.setUid(senderId);
                    // 判断是否是班级
                    if (groupId.startsWith(CLAZZ)){
                        groupId = groupId.substring(CLAZZ.length());
                        groupChatMsg.setGroupId(groupId);
                        R<Clazz> clazzR = clazzService.selectById(groupId);
                        Clazz clazz = null;
                        if (clazzR.getCode() == 200){
                            clazz = clazzR.getData();
                        }
                        assert clazz != null;
                        userIds = userService.getUserIdsByClazzId(groupId,clazz.getUserId());
                    }else {
                        R<Course> r = courseService.getById(groupId);
                        Course course = null;
                        if (r.getCode() == 200){
                            course = r.getData();
                        }
                        assert course != null;
                        R<List<Object>> rL = userCourseService.getUserIdsByCourseId(groupId, course.getUserId());
                        if (rL.getCode() == 200){
                            userIds = rL.getData();
                        }
                    }
                    if (userIds != null && !userIds.isEmpty()){
                        String finalGroupId = groupId;
                        userIds.forEach(o -> {
                            if (!senderId.equals(o)){
                                ChannelId channelId = USERS.get(o);
                                if (ObjectUtil.isNotNull(channelId)){
                                    Channel channel1 = CHANNEL_GROUPS.find(channelId);
                                    if (ObjectUtil.isNotNull(channel1) && channel1.isActive() && channel1.isOpen()) {
                                        channel1.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(new MsgVo<>(4,chatMsgVo, finalGroupId))));
                                    }
                                }
                            }
                        });
                    }
                }
            }
        }
    }
}
